using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using FoundationLib.Ifc.Domain;


namespace FoundationLib.Ifc.Domain 
{
	/// <summary>
	/// Abstract superclass for aggregates providing facilities for event sourcing
	/// </summary>
	public abstract class EventSourcedAggregate : AggregateRoot, IEventSourcedAggregate
	{
		/// <summary>
		/// A list of pending changes for this aggregate
		/// </summary>
		private readonly IPendingChanges changes = new PendingChanges();
		/// <summary>
		/// A list of event handlers on this aggregate
		/// </summary>
		private readonly Dictionary<Type, Action<IEvent>> handlers = new Dictionary<Type,Action<IEvent>>();
		/// <summary>
		/// The current aggregate version
		/// </summary>
		private int version = -1;

		/// <summary>
		/// Creates a new event-sourced aggregate instance
		/// </summary>
		protected EventSourcedAggregate()
		{
		}

		/// <summary>
		/// Applies the given event to the aggregate by invoking all registered event handlers 
		/// and adds it to the list of pending changes
		/// </summary>
		/// <param name="domainEvent">The event to be applied and enlist as pending change</param>
		protected void ApplyChange(IVersionedEvent domainEvent)
		{
			if (domainEvent == null)
				return;

			domainEvent.Version = this.version + 1;

			Action<IEvent> handler;
			if (this.handlers.TryGetValue(domainEvent.GetType(), out handler))
			{
				handler.Invoke(domainEvent);
			}

			this.version = domainEvent.Version;
			this.changes.Add(domainEvent);
		}

		/// <summary>
		/// Gets pending versioned events for this aggregate
		/// </summary>
		public IPendingChanges Changes
		{
			get
			{
				return this.changes;
			}
		}

		/// <summary>
		/// Registers the given delegate as a handler for events of type T
		/// </summary>
		/// <param name="handler">The handler action delegate</param>
		protected void Handles<T>(Action<T> handler) where T : IEvent
		{
			if (handler == null)
				throw new ArgumentNullException("handler");
			this.handlers.Add(typeof(T), (x) => handler((T)x));
		}

		/// <summary>
		/// Reconstructs the state of the entity from the given event history
		/// </summary>
		/// <param name="history">The event history</param>
		public void LoadFromHistory(IEnumerable<IVersionedEvent> history)
		{
			if (history == null)
				throw new ArgumentNullException("eventHistory");
			lock (this)
			{
				foreach (IVersionedEvent item in history)
				{
					Action<IEvent> handler;
					if (this.handlers.TryGetValue(item.GetType(), out handler))
					{
						handler.Invoke(item);
						this.version = item.Version;
					}
				}
			}
		}

		/// <summary>
		/// Gets the current version of the aggregate
		/// </summary>
		public int Version
		{
			get
			{
				return this.version;
			}
		}

	}

}